﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using StackExchange.Redis;

namespace Jack.RedisLib
{
    public partial class RedisClient
    {
        public async Task<bool> SortedSetAdd<T>(string key, T member, double score, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisMember = ConvertObjectValueToRedisValue(member);

            return await database.SortedSetAddAsync(redisKey, redisMember, score);
        }

        public async Task<long> SortedSetAdd<T>(string key, Dictionary<T, double> members, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return -1;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            SortedSetEntry[] sortedSetEntries = new SortedSetEntry[members.Count];
            int i = 0;
            foreach (KeyValuePair<T, double> kvp in members)
            {
                RedisValue redisValue = ConvertObjectValueToRedisValue(kvp.Key);
                sortedSetEntries[i++] = new SortedSetEntry(redisValue, kvp.Value);
            }

            return await database.SortedSetAddAsync(redisKey, sortedSetEntries);
        }

        public async Task<double> SortedSetDecrement<T>(string key, T member, double value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return double.NaN;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisMember = ConvertObjectValueToRedisValue(member);

            return await database.SortedSetDecrementAsync(redisKey, redisMember, value);
        }

        public async Task<double> SortedSetIncrement<T>(string key, T member, double value, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return double.NaN;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisMember = ConvertObjectValueToRedisValue(member);

            return await database.SortedSetIncrementAsync(redisKey, redisMember, value);
        }

        public async Task<long> SortedSetLength(string key, double min = double.NegativeInfinity, double max = double.PositiveInfinity, Exclude exclude = Exclude.None, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            return await database.SortedSetLengthAsync(redisKey, min, max, exclude);
        }

        public async Task<long> SortedSetLengthByValue<T>(string key, T min, T max, Exclude exclude = Exclude.None, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue minRedisValue = ConvertObjectValueToRedisValue(min);
            RedisValue maxRedisValue = ConvertObjectValueToRedisValue(max);

            return await database.SortedSetLengthByValueAsync(redisKey, minRedisValue, maxRedisValue, exclude);
        }

        public async Task<T[]> SortedSetRangeByRank<T>(string key, long start = 0L, long stop = -1L, Order order = Order.Ascending, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.SortedSetRangeByRankAsync(redisKey, start, stop, order);
            if (redisValues == null || redisValues.Length == 0) return [];

            T[] values = new T[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToObjectValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T?[]> SortedSetRangeByRankForValueType<T>(string key, long start = 0L, long stop = -1L, Order order = Order.Ascending, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.SortedSetRangeByRankAsync(redisKey, start, stop, order);
            if (redisValues == null || redisValues.Length == 0) return [];

            T?[] values = new T?[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToValueTypeValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<Dictionary<T, double>> SortedSetRangeByRankWithScores<T>(string key, long start = 0L, long stop = -1L, Order order = Order.Ascending, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            SortedSetEntry[] sortedSetEntries = await database.SortedSetRangeByRankWithScoresAsync(redisKey, start, stop, order);

            Dictionary<T, double> values = [];
            foreach (var sortedSetEntry in sortedSetEntries)
            {
                T member = ConvertRedisValueToObjectValue<T>(sortedSetEntry.Element);
                values[member] = sortedSetEntry.Score;
            }

            return values;
        }

        public async Task<Dictionary<T?, double>> SortedSetRangeByRankWithScoresForValueType<T>(string key, long start = 0L, long stop = -1L, Order order = Order.Ascending, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            SortedSetEntry[] sortedSetEntries = await database.SortedSetRangeByRankWithScoresAsync(redisKey, start, stop, order);

            Dictionary<T?, double> values = [];
            foreach (var sortedSetEntry in sortedSetEntries)
            {
                T? member = ConvertRedisValueToValueTypeValue<T>(sortedSetEntry.Element);
                values[member] = sortedSetEntry.Score;
            }

            return values;
        }

        public async Task<T[]> SortedSetRangeByScore<T>(string key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0L, long take = -1L, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.SortedSetRangeByScoreAsync(redisKey, start, stop, exclude, order, skip, take);
            if (redisValues == null || redisValues.Length == 0) return [];

            T[] values = new T[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToObjectValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T?[]> SortedSetRangeByScoreForValueType<T>(string key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0L, long take = -1L, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisValues = await database.SortedSetRangeByScoreAsync(redisKey, start, stop, exclude, order, skip, take);
            if (redisValues == null || redisValues.Length == 0) return [];

            T?[] values = new T?[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToValueTypeValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T[]> SortedSetRangeByValue<T>(string key, T min = default, T max = default, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0L, long take = -1L, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue minValue = ConvertObjectValueToRedisValue(min);
            RedisValue maxValue = ConvertObjectValueToRedisValue(max);
            RedisValue[] redisValues = await database.SortedSetRangeByValueAsync(redisKey, minValue, maxValue, exclude, order, skip, take);
            if (redisValues == null || redisValues.Length == 0) return [];

            T[] values = new T[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToObjectValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<T?[]> SortedSetRangeByValueForValueType<T>(string key, T min = default, T max = default, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0L, long take = -1L, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue minValue = ConvertObjectValueToRedisValue(min);
            RedisValue maxValue = ConvertObjectValueToRedisValue(max);
            RedisValue[] redisValues = await database.SortedSetRangeByValueAsync(redisKey, minValue, maxValue, exclude, order, skip, take);
            if (redisValues == null || redisValues.Length == 0) return [];

            T?[] values = new T?[redisValues.Length];
            for (int i = 0; i < redisValues.Length; i++)
            {
                values[i] = ConvertRedisValueToValueTypeValue<T>(redisValues[i]);
            }

            return values;
        }

        public async Task<long?> SortedSetRank<T>(string key, T member, Order order = Order.Ascending, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisMember = ConvertObjectValueToRedisValue(member);

            return await database.SortedSetRankAsync(redisKey, redisMember, order);
        }

        public async Task<bool> SortedSetRemove<T>(string key, T member, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return false;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisMember = ConvertObjectValueToRedisValue(member);

            return await database.SortedSetRemoveAsync(redisKey, redisMember);
        }

        public async Task<long> SortedSetRemove<T>(string key, List<T> members, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return 0;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue[] redisMembers = BatchConvertObjectValueToRedisValue(members);

            return await database.SortedSetRemoveAsync(redisKey, redisMembers);
        }

        public async Task<long> SortedSetRemoveRangeByRank(string key, long start, long stop, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return 0;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            return await database.SortedSetRemoveRangeByRankAsync(redisKey, start, stop);
        }

        public async Task<long> SortedSetRemoveRangeByScore(string key, double start, double stop, Exclude exclude = Exclude.None, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return 0;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            return await database.SortedSetRemoveRangeByScoreAsync(redisKey, start, stop, exclude);
        }

        public async Task<long> SortedSetRemoveRangeByValue<T>(string key, T min, T max, Exclude exclude = Exclude.None, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return 0;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue minRedisValue = ConvertObjectValueToRedisValue(min);
            RedisValue maxRedisValue = ConvertObjectValueToRedisValue(max);

            return await database.SortedSetRemoveRangeByValueAsync(redisKey, minRedisValue, maxRedisValue, exclude);
        }

        public async IAsyncEnumerable<(T Element, double Score)> SortedSetScan<T>(string key, string pattern = default, int pageSize = 250, long cursor = 0L, int pageOffset = 0, int db = -1, [EnumeratorCancellation] CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) yield break;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisPattern = ConvertObjectValueToRedisValue(pattern);

            await foreach (var sortedSetEntry in database.SortedSetScanAsync(redisKey, redisPattern, pageSize, cursor, pageOffset))
            {
                T value = ConvertRedisValueToObjectValue<T>(sortedSetEntry.Element);
                yield return (value, sortedSetEntry.Score);
            }
        }

        public async IAsyncEnumerable<(T? Element, double Score)> SortedSetScanForValueType<T>(string key, string pattern = default, int pageSize = 250, long cursor = 0L, int pageOffset = 0, int db = -1, [EnumeratorCancellation] CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) yield break;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisPattern = ConvertObjectValueToRedisValue(pattern);

            await foreach (var sortedSetEntry in database.SortedSetScanAsync(redisKey, redisPattern, pageSize, cursor, pageOffset))
            {
                T? value = ConvertRedisValueToValueTypeValue<T>(sortedSetEntry.Element);
                yield return (value, sortedSetEntry.Score);
            }
        }

        public async Task<double?> SortedSetScore<T>(string key, T member, int db = -1, CancellationToken cancellationToken = default)
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            RedisValue redisMember = ConvertObjectValueToRedisValue(member);

            return await database.SortedSetScoreAsync(redisKey, redisMember);
        }

        public async Task<(T Element, double Score)?> SortedSetPop<T>(string key, Order order = Order.Ascending, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            SortedSetEntry? entry = await database.SortedSetPopAsync(redisKey, order);
            if (entry == null) return default;

            T element = ConvertRedisValueToObjectValue<T>(entry.Value.Element);
            return (element, entry.Value.Score);
        }

        public async Task<(T? Element, double Score)?> SortedSetPopForValueType<T>(string key, Order order = Order.Ascending, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return default;

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            SortedSetEntry? entry = await database.SortedSetPopAsync(redisKey, order);
            if (entry == null) return default;

            T? element = ConvertRedisValueToValueTypeValue<T>(entry.Value.Element);
            return (element, entry.Value.Score);
        }

        public async Task<Dictionary<T, double>> SortedSetPop<T>(string key, long count, Order order = Order.Ascending, int db = -1, CancellationToken cancellationToken = default)
            where T : class
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            SortedSetEntry[] entries = await database.SortedSetPopAsync(redisKey, count, order);
            if (entries == null || entries.Length == 0) return [];

            Dictionary<T, double> values = [];
            foreach (var entry in entries)
            {
                T element = ConvertRedisValueToObjectValue<T>(entry.Element);
                values[element] = entry.Score;
            }

            return values;
        }

        public async Task<Dictionary<T?, double>> SortedSetPopForValueType<T>(string key, long count, Order order = Order.Ascending, int db = -1, CancellationToken cancellationToken = default)
            where T : struct
        {
            IDatabase database = await GetConnectedDatabase(db, cancellationToken);
            if (database == null) return [];

            RedisKey redisKey = ConvertStringKeyToRedisKey(key);
            SortedSetEntry[] entries = await database.SortedSetPopAsync(redisKey, count, order);
            if (entries == null || entries.Length == 0) return [];

            Dictionary<T?, double> values = [];
            foreach (var entry in entries)
            {
                T? element = ConvertRedisValueToValueTypeValue<T>(entry.Element);
                values[element] = entry.Score;
            }

            return values;
        }
    }
}
